class Compiler {
  constructor(vm) {
    this.el = vm.$el  // 记录模板
    this.vm = vm      // 记录实例
    this.compile(this.el)  // 创建模板，编译对象
  }


  // 编译模板，处理文本节点和元素节点
  compile(el) {

    // children 是子元素，childNodes 是子节点
    let childNodes = el.childNodes

    // childNodes是个伪数组，需要Array.from(childNodes) 进行转化成真正的数组
    Array.from(childNodes).forEach(node => {

      if (this.isTextNode(node)) { // 处理文本节点
        this.compileText(node)
      } else if (this.isElementNode(node)) { // 处理元素节点
        this.compileElement(node)
      }

      // 判断node节点，是否有子节点，如果有子节点，要递归调用compile
      if (node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })

  }


  // 编译元素节点，处理指令
  compileElement(node) {
    // console.log(node.attributes) // 获取所有的属性节点： node.attributes，伪数组

    // 遍历所有的属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否是指令
      let attrName = attr.name  // name属性就是  v-text之类的，value就是属性的值

      if (this.isDirective(attrName)) {  // 是否以 v- 开头，判断是否是指令
        attrName = attrName.substr(2)  // // v-text --> text  属性名
        let key = attr.value     //  属性值
        this.update(node, key, attrName)

        /*
        这里还可以区分
        if (attrName.indexOf('@') == 0) {
          //事件类型
          const type = attrName.substring(1);
          console.log(type);
          //执行
          this.handleEvent(node, this.$vm, key, type) 
          // type 事件类型，比如click，change 等
        }
      */
      }
    })
  }

  update(node, key, attrName) {
    let updateFn = this[attrName + 'Updater'] // 定义指令名的方法，可变化的

    // call 方法改变 this 的指向，指向 Compiler 对象
    updateFn && updateFn.call(this, node, this.vm[key], key)
  }

  // 处理 v-text 指令
  textUpdater(node, value, key) {
    node.textContent = value

    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue // 将插值表达式变为 this.vm.$data的值
    })
  }


  // 处理 v-model 指令
  modelUpdater(node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })

    // 双向绑定  实现响应式数据
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }

  // shang   自己补充的   v-html    v-on
  // 处理 v-on 指令
  handleEvent(node, vm, key, eventType) {
    const fn = this.vm.$options.methods && this.vm.$options.methods[key]
    if (type && fn) {
      node.addEventListener(eventType, fn.bind(vm))
    }
  }

  // 处理 v-html 指令
  html(node, key) { //k-html
    // this.update(node, key, "html");
    // // console.log(node, key, this.$vm[key], 'v-html')  
    node.innerHTML = value
    new Watcher(this.vm, key, (newValue) => {
      node.innerHTML = newValue
    })
  }


  // 编译文本节点，处理差值表达式
  compileText(node) {
    // console.dir(node)
    // {{  msg }}
    let reg = /\{\{(.+?)\}\}/     //  想要提取变量名，加小括号 (.+?)
    let value = node.textContent  //  获取文本节点的内容

    if (reg.test(value)) {
      let key = RegExp.$1.trim()   // $1 是获取第一个小括号的分组内容，第二个$2
      // 把原来的文本节点的差值表达式 的内容，替换成属性对应的值
      node.textContent = value.replace(reg, this.vm[key])

      // 创建watcher对象，当数据改变更新视图
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }


  // 判断元素属性是否是指令
  isDirective(attrName) {
    return attrName.startsWith('v-')
  }


  // 判断节点是否是文本节点
  isTextNode(node) {
    return node.nodeType === 3
  }


  // 判断节点是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1
  }

}